/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "quadspi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifndef __W25Q64JV_H__
#define __W25Q64JV_H__

#define W25Q64JV_FLASH_SIZE (8 * 1024 * 1024 * 2)
#define W25Q64JV_BLOCK_SIZE (32 * 1024 * 2)
#define W25Q64JV_SECTOR_SIZE (4096 * 2)
#define W25Q64JV_PAGE_SIZE (256 * 2)

#define QSPI_ERROR (-1)
#define QSPI_OK (0)

#define W25Q64JV_WRITE_ENABLE (0x06)
#define W25Q64JV_INPUT_FAST_READ (0xeb)
#define W25Q64JV_PAGE_PROGRAM (0x02)
#define W25Q64JV_STATUS_REG1 (0x05)
#define W25Q64JV_ENABLE_RESET (0x66)
#define W25Q64JV_RESET_DEVICE (0x99)
#define W25Q64JV_DEVICE_ID (0x90)
#define W25Q64JV_ID_NUMBER (0x4b)
#define W25Q64JV_ERASE_SECTOR (0x20)
#define W25Q64JV_ERASE_Block32K (0x52)
#define W25Q64JV_ERASE_Block64K (0xD8)
#define W25Q64JV_ERASE_CHIP (0xc7)

extern int QSPI_W25Q64JV_WriteEnable(void);
extern int QSPI_W25Q64JV_Reset(void);
extern int QSPI_W25Q64JV_DeviceID(uint8_t *v);
extern int QSPI_W25Q64JV_IDNumber(uint8_t *v);
extern int QSPI_W25Q64JV_EraseBlock_32Kx2(uint32_t BlockAddress);
extern int QSPI_W25Q64JV_EraseChip(void);
extern int QSPI_W25Q64JV_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size);
extern int QSPI_W25Q64JV_PageProgram(uint8_t *pData, uint32_t ReadAddr, uint32_t Size);
extern int QSPI_W25Q64JV_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size);
extern int QSPI_W25Q64JV_EnableMemoryMappedMode(void);

#endif

static int QSPI_W25Q64JV_AutoPollingMemReady(uint32_t timeout)
{
  QSPI_CommandTypeDef sCmd;
  QSPI_AutoPollingTypeDef sConf;
  memset(&sCmd, 0, sizeof(sCmd));
  memset(&sConf, 0, sizeof(sConf));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_STATUS_REG1;

  sCmd.DataMode = QSPI_DATA_1_LINE;
  sCmd.DummyCycles = 0;

  sConf.Match = 0x0000;
  sConf.Mask = 0x0101;
  sConf.MatchMode = QSPI_MATCH_MODE_AND;
  sConf.StatusBytesSize = 2;
  sConf.Interval = 0x10;
  sConf.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

  if (HAL_QSPI_AutoPolling(&hqspi, &sCmd, &sConf, timeout) != HAL_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_DeviceID(uint8_t *v)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_DEVICE_ID;

  sCmd.AddressMode = QSPI_ADDRESS_1_LINE;
  sCmd.AddressSize = QSPI_ADDRESS_24_BITS;
  sCmd.Address = 0;

  sCmd.DataMode = QSPI_DATA_1_LINE;
  sCmd.DummyCycles = 0;
  sCmd.NbData = 4;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (HAL_QSPI_Receive(&hqspi, (uint8_t *)v, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_WriteEnable(void)
{
  QSPI_CommandTypeDef sCmd;
  QSPI_AutoPollingTypeDef sConf;
  memset(&sCmd, 0, sizeof(sCmd));
  memset(&sConf, 0, sizeof(sConf));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_WRITE_ENABLE;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;

  /* Configure automatic polling mode to wait for write enabling */
  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_STATUS_REG1;

  sCmd.DataMode = QSPI_DATA_1_LINE;
  sCmd.DummyCycles = 0;

  sConf.Match = 0x0202;
  sConf.Mask = 0x0202;
  sConf.MatchMode = QSPI_MATCH_MODE_AND;
  sConf.StatusBytesSize = 2;
  sConf.Interval = 0x10;
  sConf.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

  if (HAL_QSPI_AutoPolling(&hqspi, &sCmd, &sConf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_Reset(void)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_ENABLE_RESET;
  if (HAL_QSPI_Command(&hqspi, &sCmd, 0) != HAL_OK) return QSPI_ERROR;

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_RESET_DEVICE;
  if (HAL_QSPI_Command(&hqspi, &sCmd, 0) != HAL_OK) return QSPI_ERROR;

  if (QSPI_W25Q64JV_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_IDNumber(uint8_t *v)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_ID_NUMBER;

  sCmd.DataMode = QSPI_DATA_1_LINE;
  sCmd.DummyCycles = 0;
  sCmd.NbData = 24;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;

  if (HAL_QSPI_Receive(&hqspi, (uint8_t *)v, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_INPUT_FAST_READ;

  sCmd.AddressMode = QSPI_ADDRESS_4_LINES;
  sCmd.AddressSize = QSPI_ADDRESS_24_BITS;
  sCmd.Address = ReadAddr;

  //sCmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES;
  //sCmd.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
  //sCmd.AlternateBytes = 0;

  sCmd.DataMode = QSPI_DATA_4_LINES;
  sCmd.DummyCycles = 6;
  sCmd.NbData = Size;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (QSPI_W25Q64JV_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_EraseBlock_32Kx2(uint32_t BlockAddress)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  if (QSPI_W25Q64JV_WriteEnable() != QSPI_OK) return QSPI_ERROR;

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_ERASE_Block32K;

  sCmd.AddressMode = QSPI_ADDRESS_1_LINE;
  sCmd.AddressSize = QSPI_ADDRESS_24_BITS;
  sCmd.Address = BlockAddress;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (QSPI_W25Q64JV_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_EraseChip(void)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  if (QSPI_W25Q64JV_WriteEnable() != QSPI_OK) return QSPI_ERROR;

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_ERASE_CHIP;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (QSPI_W25Q64JV_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_PageProgram(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
  QSPI_CommandTypeDef sCmd;
  memset(&sCmd, 0, sizeof(sCmd));

  if (QSPI_W25Q64JV_WriteEnable() != QSPI_OK) return QSPI_ERROR;

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_PAGE_PROGRAM;

  sCmd.AddressMode = QSPI_ADDRESS_1_LINE;
  sCmd.AddressSize = QSPI_ADDRESS_24_BITS;
  sCmd.Address = ReadAddr;

  sCmd.DataMode = QSPI_DATA_1_LINE;
  sCmd.DummyCycles = 0;
  sCmd.NbData = Size;

  if (HAL_QSPI_Command(&hqspi, &sCmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return QSPI_ERROR;
  if (QSPI_W25Q64JV_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK) return QSPI_ERROR;

  return QSPI_OK;
}

int QSPI_W25Q64JV_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{
  int ret = QSPI_OK;
  uint32_t end_addr, current_size, current_addr;
  uint8_t *write_data;

  /* Calculation of the size between the write address and the end of the page */
  current_size = W25Q64JV_PAGE_SIZE - (WriteAddr % W25Q64JV_PAGE_SIZE);

  /* Check if the size of the data is less than the remaining place in the page */
  if (current_size > Size)
  {
    current_size = Size;
  }

  /* Initialize the address variables */
  current_addr = WriteAddr;
  end_addr = WriteAddr + Size;
  write_data = pData;

  /* Perform the write page by page */
  do
  {
    /* Issue page program command */
    if (QSPI_W25Q64JV_PageProgram(write_data, current_addr, current_size) != QSPI_OK)
    {
        ret = QSPI_ERROR;
    }
    else
    {
      /* Update the address and size variables for next page programming */
      current_addr += current_size;
      write_data += current_size;
      current_size = ((current_addr + W25Q64JV_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : W25Q64JV_PAGE_SIZE;
    }
  } while ((current_addr < end_addr) && (ret == QSPI_OK));

  /* Return BSP status */
  return ret;
}

int QSPI_W25Q64JV_EnableMemoryMappedMode(void)
{
  QSPI_CommandTypeDef sCmd;
  QSPI_MemoryMappedTypeDef sMapCfg;
  memset(&sCmd, 0, sizeof(sCmd));
  memset(&sMapCfg, 0, sizeof(sMapCfg));

  sCmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  sCmd.Instruction = W25Q64JV_INPUT_FAST_READ;

  sCmd.AddressMode = QSPI_ADDRESS_4_LINES;
  sCmd.AddressSize = QSPI_ADDRESS_24_BITS;
  sCmd.Address = 0;

  //sCmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_4_LINES;
  //sCmd.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;
  //sCmd.AlternateBytes = 0;

  sCmd.DataMode = QSPI_DATA_4_LINES;
  sCmd.DummyCycles = 6;
  sCmd.NbData = 0;

  sMapCfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
  if (HAL_QSPI_MemoryMapped(&hqspi, &sCmd, &sMapCfg) != HAL_OK) return QSPI_ERROR;

  return QSPI_OK;
}

#define APPLICATION_ADDRESS 0x90000000UL
typedef void (*appFun)(void);
appFun App;
static void GoToApp(void)
{
    __set_PRIMASK(1);
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;
    for (int i = 0; i < 8; i++)
    {
        NVIC->ICER[i] = 0xFFFFFFFF;
        NVIC->ICPR[i] = 0xFFFFFFFF;
    }
    __set_PRIMASK(0);
    uint32_t *vector_table = (uint32_t *)APPLICATION_ADDRESS;
    uint32_t stack_point = vector_table[0];
    uint32_t entry_point = vector_table[1];
    if ((entry_point & 0xf0000000) == APPLICATION_ADDRESS)
    {
      App = (appFun)entry_point;
      __set_MSP(stack_point);
      __set_CONTROL(0);
      App();
    }
    else
    {
      QUADSPI->CCR = QUADSPI->CCR & 0xf7ffffff; // Indirect read mode
      QUADSPI->CR = QUADSPI->CR & 0xfffffffe;   // Disable the QUADSPI.
    }
}

uint8_t pagebuf[256];
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_QUADSPI_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("STM32H750VB Demo. \r\n");
  uint8_t id[4];
  uint8_t uid[24];
  QSPI_W25Q64JV_DeviceID(id);
  QSPI_W25Q64JV_Reset();
  QSPI_W25Q64JV_DeviceID(id);
  printf("W25QXX Manufacturer / Device ID(#1): 0x%02x 0x%02x \r\n", id[0], id[2]);
  printf("W25QXX Manufacturer / Device ID(#2): 0x%02x 0x%02x \r\n", id[1], id[3]);

  QSPI_W25Q64JV_IDNumber(uid);
  printf("W25QXX Unique ID Number(#1): ");
  for (int i = 8; i < 24; i += 2)
  {
      printf("0x%02x ", uid[i]);
  }
  printf("\r\n");
  printf("W25QXX Unique ID Number(#2): ");
  for (int i = 9; i < 24; i += 2)
  {
      printf("0x%02x ", uid[i]);
  }
  printf("\r\n");
#if 0
  QSPI_W25Q64JV_Read(pagebuf, 0, sizeof(pagebuf));
  printf("Page buffer(#0): \r\n");
  for (int i = 0; i < sizeof(pagebuf); i++)
  {
    printf("0x%02x ", pagebuf[i]);
    if ((i + 1) % 8 == 0) printf("\r\n");
  }
  printf("\r\n");

  QSPI_W25Q64JV_EraseBlock_32Kx2(0);
  QSPI_W25Q64JV_Read(pagebuf, 0, sizeof(pagebuf));
  printf("Page buffer(#0): \r\n");
  for (int i = 0; i < sizeof(pagebuf); i++)
  {
    printf("0x%02x ", pagebuf[i]);
    if ((i + 1) % 8 == 0) printf("\r\n");
  }
  printf("\r\n");

  QSPI_W25Q64JV_Read(pagebuf, 64*1024, sizeof(pagebuf));
  printf("Page buffer(#256): \r\n");
  for (int i = 0; i < sizeof(pagebuf); i++)
  {
    printf("0x%02x ", pagebuf[i]);
    if ((i + 1) % 8 == 0) printf("\r\n");
  }
  printf("\r\n");
#endif

#if 0
  memset(pagebuf, 0xa5, sizeof(pagebuf));
  QSPI_W25Q64JV_Write(pagebuf, 0, sizeof(pagebuf));

  QSPI_W25Q64JV_EnableMemoryMappedMode();
  uint8_t *pflash = (uint8_t *)0x90000000;
  printf("Address(0x90000000): \r\n");
  for (int i = 0; i < 256; i++)
  {
    printf("0x%02x ", *pflash++);
    if ((i + 1) % 8 == 0) printf("\r\n");
  }
  printf("\r\n");

  QUADSPI->CCR = QUADSPI->CCR & 0xf7ffffff; // Indirect read mode
  QUADSPI->CR = QUADSPI->CR & 0xfffffffe;   // Disable the QUADSPI.

  volatile uint8_t check = *pflash;         // HardFault_Handler()
#endif
  if (QSPI_W25Q64JV_EnableMemoryMappedMode() == QSPI_OK)
  {
    GoToApp();
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Supply configuration update enable
  */
  HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
